<html>
  <head>
    <meta charset="utf-8" />
    <style>
html, body {
    margin: 0;
    padding: 0;
}
#header {
    position: fixed;
    top: 0;
    left: 0;
    height: 50px;
    line-height: 50px;
    width: 100%;
    background-color: #999;
    box-shadow: 0px 0px 5px 0px rgba(0,0,0,0.5);
    font-family: Helvetica, sans-serif;
}
#header, #header a {
    color: white;
}
#downloads {
    float: right;
    background: #999;
}
.download {
    float: right;
    background: #999;
    padding: 0 5px;
}
.home {
  margin: 0;
  font-size: 125%;
  font-weight: lighter;
  text-transform: lowercase;
}
.home a {
  margin: 0;
  background: #666;
  padding-left: 25px;
  padding-right: 30px;
  margin-right: 20px;
  float: left;
  text-decoration: none;
}
.home:hover a {
  background: #555;
}
#audio {
    margin-top: 9px;
    width: 50%;
    display: inline-block;
}
#transcript {
    margin: 0 15px;
    margin-top: 70px;
    margin-bottom: 5em;
    white-space: pre-wrap;
    line-height: 2em;
    max-width: 600px;
    color: #999;
}
#transcript.status {
    background-color: #333;
    color: #fff;
    font-family: Courier, mono;
    line-height: 1em;
    font-size: 10pt;
    max-width: 100%;
}
#transcript.status h2 {
    padding: 10px;
}
#transcript.status .entry {
    margin-bottom: 10px;
    padding: 10px;
}
#transcript.status progress {
    width: 100%;
    height: 30px;
    margin-bottom: 20px;
}
.success {
    color: black;
}
.success:hover {
    text-decoration: underline;
}
.active {
    color: magenta;
}
#preloader {
    visibility: hidden;
}
.phactive {
    text-decoration: underline;
}
.phones {
    position: absolute;
    color: #333;
}
.phones .phone {
    margin-right: 5px;
    font-family: Helvetica, sans-serif;
    text-transform: uppercase;
    font-size: 50%;
}
.phones .phone:last-child {
    margin-right: 0;
}
#footer {
  margin-top: 100px;
  border-top: 1px dotted black;
  font-size: 8pt;
  font-style: italic;
  font-family: Helvetica, sans-serif;
  padding: 10px;
}
    </style>
  </head>
  <body>
    <div id="header">
      <h1 class="home"><a href="/">Gentle</a></h1>
      <audio id="audio" src="a.wav" controls="true"></audio>
      <img src="/preloader.gif" id="preloader" alt="loading...">
      <span id="downloads"> </div>
    </div>
    <div id="transcript"></div>
    <div id="footer">
      <a href="https://lowerquality.com/gentle">Gentle</a> is free software released under the <a href="https://opensource.org/licenses/MIT">MIT license</a>. <a href="https://lowerquality.com/gentle">Homepage</a> | <a href="https://github.com/lowerquality/gentle">Source code</a>.
    </div>

    <script>

function get(url, cb) {
    var xhr = new XMLHttpRequest();
    xhr.open("GET", url, true);
    xhr.onload = function() {
        cb(this.responseText);
    }
    xhr.send();
}
function get_json(url, cb) {
    get(url, function(x) {
        cb(JSON.parse(x));
    });
}

var $a = document.getElementById("audio");
window.onkeydown = function(ev) {
    if(ev.keyCode == 32) {
        ev.preventDefault();
        $a.pause();
    }
}

var $trans = document.getElementById("transcript");
var $preloader = document.getElementById('preloader');

var wds = [];
var cur_wd;

var $phones = document.createElement("div");
$phones.className = "phones";
document.body.appendChild($phones);

var cur_phones$ = [];           // List of phoneme $divs
var $active_phone;

function render_phones(wd) {
    cur_phones$ = [];
    $phones.innerHTML = "";
    $active_phone = null;

    $phones.style.top = wd.$div.offsetTop + 18;
    $phones.style.left = wd.$div.offsetLeft;

    var dur = wd.end - wd.start;

    var start_x = wd.$div.offsetLeft;
    
    wd.phones
        .forEach(function(ph){
            var $p = document.createElement("span");
            $p.className = "phone";
            $p.textContent = ph.phone.split("_")[0];
            
            $phones.appendChild($p);
            cur_phones$.push($p);
        });

    var offsetToCenter = (wd.$div.offsetWidth - $phones.offsetWidth) / 2;
    $phones.style.left = wd.$div.offsetLeft + offsetToCenter;
}
function highlight_phone(t) {
    if(!cur_wd) {
        $phones.innerHTML = "";
        return;
    }
    var hit;
    var cur_t = cur_wd.start;
    
    cur_wd.phones.forEach(function(ph, idx) {
        if(cur_t <= t && cur_t + ph.duration >= t) {
            hit = idx;
        }
        cur_t += ph.duration;
    });

    if(hit) {
        var $ph = cur_phones$[hit];
        if($ph != $active_phone) {
            if($active_phone) {
                $active_phone.classList.remove("phactive");
            }
            if($ph) {
                $ph.classList.add("phactive");
            }
        }
        $active_phone = $ph;
    }
}

function highlight_word() {
    var t = $a.currentTime;
    // XXX: O(N); use binary search
    var hits = wds.filter(function(x) {
        return (t - x.start) > 0.01 && (x.end - t) > 0.01;
    }, wds);
    var next_wd = hits[hits.length - 1];

    if(cur_wd != next_wd) {
        var active = document.querySelectorAll('.active');
        for(var i = 0; i < active.length; i++) {
            active[i].classList.remove('active');
        }
        if(next_wd && next_wd.$div) {
            next_wd.$div.classList.add('active');
            render_phones(next_wd);
        }
    }
    cur_wd = next_wd;
    highlight_phone(t);

    window.requestAnimationFrame(highlight_word);
}
window.requestAnimationFrame(highlight_word);

$trans.innerHTML = "Loading...";

function render(ret) {
    wds = ret['words'] || [];
    transcript = ret['transcript'];

    $trans.innerHTML = '';

    var currentOffset = 0;

    wds.forEach(function(wd) {
        if(wd.case == 'not-found-in-transcript') {
            // TODO: show phonemes somewhere
            var txt = ' ' + wd.word;
            var $plaintext = document.createTextNode(txt);
            $trans.appendChild($plaintext);
            return;
        }

        // Add non-linked text
        if(wd.startOffset > currentOffset) {
            var txt = transcript.slice(currentOffset, wd.startOffset);
            var $plaintext = document.createTextNode(txt);
            $trans.appendChild($plaintext);
            currentOffset = wd.startOffset;
        }

        var $wd = document.createElement('span');
        var txt = transcript.slice(wd.startOffset, wd.endOffset);
        var $wdText = document.createTextNode(txt);
        $wd.appendChild($wdText);
        wd.$div = $wd;
        if(wd.start !== undefined) {
            $wd.className = 'success';
        }
        $wd.onclick = function() {
            if(wd.start !== undefined) {
                console.log(wd.start);
                $a.currentTime = wd.start;
                $a.play();
            }
        };
        $trans.appendChild($wd);
        currentOffset = wd.endOffset;
    });

    var txt = transcript.slice(currentOffset, transcript.length);
    var $plaintext = document.createTextNode(txt);
    $trans.appendChild($plaintext);
    currentOffset = transcript.length;
}

function show_downloads() {
    var $d = document.getElementById("downloads");
    $d.textContent = "Download as: ";
    var uid = window.location.pathname.split("/")[2];
    // Name, path, title, inhibit-on-file:///
    [["CSV", "align.csv", "Word alignment CSV"],
     ["JSON", "align.json", "JSON word/phoneme alignment data"],
     ["Zip", "/zip/" + uid + ".zip", "Standalone zipfile", true]]
        .forEach(function(x) {
            var $a = document.createElement("a");
            $a.className = "download";
            $a.textContent = x[0];
            $a.href = x[1];
            $a.title = x[2];
            if(!x[3] || window.location.protocol != "file:") {
                $d.appendChild($a);
            }
        });
}

var status_init = false;
var status_log  = [];		// [ status ]
var $status_pro;

function render_status(ret) {
    if(!status_init) {
	// Clobber the $trans div and use it for status updates
	$trans.innerHTML = "<h2>transcription in progress</h2>";
	$trans.className = "status";
	$status_pro = document.createElement("progress");
	$status_pro.setAttribute("min", "0");
	$status_pro.setAttribute("max", "100");
	$status_pro.value = 0;
	$trans.appendChild($status_pro);
	
	status_init = true;
    }
    if(ret.status !== "TRANSCRIBING") {
	if(ret.percent) {
	    $status_pro.value = (100*ret.percent);
	}
    }
    else if(ret.percent && (status_log.length == 0 || status_log[status_log.length-1].percent+0.0001 < ret.percent)) {
	// New entry
	var $entry = document.createElement("div");
	$entry.className = "entry";
	$entry.textContent = ret.message;
	ret.$div = $entry;

	if(ret.percent) {
	    $status_pro.value = (100*ret.percent);
	}

	if(status_log.length > 0) {
	    $trans.insertBefore($entry, status_log[status_log.length-1].$div);
	}
	else {
	    $trans.appendChild($entry);
	}
	status_log.push(ret);
    }
}

function update() {
    if(INLINE_JSON) {
        // We want this to work from file:/// domains, so we provide a
        // mechanism for inlining the alignment data.
        render(INLINE_JSON);
        show_downloads();
    }
    else  {
	// Show the status
        get_json('status.json', function(ret) {
	    $a.style.visibility = 'hidden';
            if (ret.status == 'ERROR') {
                $preloader.style.visibility = 'hidden';
                $trans.innerHTML = '<b>' + ret.status + ': ' + ret.error + '</b>';
            } else if (ret.status == 'TRANSCRIBING' || ret.status == 'ALIGNING') {
                $preloader.style.visibility = 'visible';
                render_status(ret);
                setTimeout(update, 2000);
            } else if (ret.status == 'OK') {
                $preloader.style.visibility = 'hidden';
		// XXX: should we fetch the align.json?
		window.location.reload();
                show_downloads();
            } else if (ret.status == 'ENCODING' || ret.status == 'STARTED') {
                $preloader.style.visibility = 'visible';
                $trans.innerHTML = 'Encoding, please wait...';
                setTimeout(update, 2000);
            } else {
		console.log("unknown status", ret);
                $preloader.style.visibility = 'hidden';
                $trans.innerHTML = ret.status + '...';
                setTimeout(update, 5000);		
            }
        });
    }
}

var INLINE_JSON;

update();

</script></body></html>
